/**
* Copyright (C) 2000 - 2009 Silverpeas
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* As a special exception to the terms and conditions of version 3.0 of the GPL, you may
* redistribute this Program in connection with Free/Libre Open Source Software ("FLOSS")
* applications as described in Silverpeas's FLOSS exception. You should have received a copy of the
* text describing the FLOSS exception, and it is also available here:
* "http://repository.silverpeas.com/legal/licensing"
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package org.silverpeas.applicationbuilder;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import org.jdom.Content;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.silverpeas.xml.ClasspathEntityResolver;
/**
* Represents an XML Document and provides convenient methods. The methods are used basically to
* load a document (parse it and obtain a tree representation), save a document (save the tree
* representation to a well formed XML file). More sophisticated methods are used to merge the
* document with another one and to sort the tags in the document (it is needed when used in
* application server).
* @author Silverpeas
* @version 1.0
* @since 1.0
*/
public class XmlDocument extends ApplicationBuilderItem {
// private String doctypeDecl = null;
private XMLOutputter outputter = null;
/**
* @since 1.0
*/
private org.jdom.Document underlyingDocument = null;
/**
* @since 1.0
*/
private java.lang.String outputEncoding = "UTF-8";
public XmlDocument() {
}
public XmlDocument(String directory, String name) {
super(directory, name);
setOutputter();
}
public XmlDocument(File directory, String name) {
super(directory, name);
setOutputter();
}
/**
* Save the document tree in the file item
* @since 1.0
* @roseuid 3AAF323B0003
*/
public void save() throws AppBuilderException {
try {
saveTo(new FileOutputStream(getPath()));
} catch (FileNotFoundException fnfe) {
throw new AppBuilderException("Could not save \""
+ getPath().getAbsolutePath() + "\"", fnfe);
}
}
/**
* Save the document tree to a stream. This is convenient for writing in an archive
* @roseuid 3AAF41A601C1
*/
public void saveTo(java.io.OutputStream outStream) throws AppBuilderException {
try {
getOutputter().output(getDocument(), outStream);
} catch (IOException ioe) {
throw new AppBuilderException("Could not save " + getName()
+ " to output stream", ioe);
}
}
/**
* Loads the document tree from the file system
* @roseuid 3AAF337D004C
*/
public void load() throws AppBuilderException {
if (!getPath().exists()) {
throw new AppBuilderException("\"" + getPath().getAbsolutePath()
+ "\" does not exist");
}
try {
loadFrom(getPath().toURI().toURL().openStream());
} catch (java.net.MalformedURLException mue) {
throw new AppBuilderException("Could not load \""
+ getPath().getAbsolutePath() + "\"", mue);
} catch (java.io.IOException ioe) {
throw new AppBuilderException("Could not load \""
+ getPath().getAbsolutePath() + "\"", ioe);
}
}
/**
* Loads the document tree from the contents of an XML file provided as a stream. This can happen
* when loading from an archive.
* @param xmlStream the contents of an XML file
* @throws AppBuilderException
* @since 1.0
* @roseuid 3AAF4099035F
*/
public void loadFrom(InputStream xmlStream) throws AppBuilderException {
// Attention a la configuration HTTP ! (Proxy : sys. props.
// "http.proxy[Host|Port])
// pour acces au DOCTYPE
try {
SAXBuilder builder = new SAXBuilder(false);
builder.setEntityResolver(new ClasspathEntityResolver(builder.getEntityResolver()));
underlyingDocument = builder.build(xmlStream);
} catch (IOException jde) {
throw new AppBuilderException("Could not load \"" + getName()
+ "\" from input stream", jde);
} catch (JDOMException jde) {
throw new AppBuilderException("Could not load \"" + getName()
+ "\" from input stream", jde);
}
}
/**
* Merges only the children of the root element of each document. It takes all the elements
* concerned by the array of tags from all the documents to merge and adds them to the resulting
* document. <strong>In the resulting document, the comments, processing instructions and entities
* are removed.</strong>
* @param tagsToMerge
* @param xmlFile
* @throws AppBuilderException
* @roseuid 3AAF3793006E
*/
public void mergeWith(String[] tagsToMerge, XmlDocument xmlFile) throws AppBuilderException {
/**
* gets the resulting document from the master document. Cloning the document is important. If
* you clone or copy an element, the copy keeps his owner and, as a result, the element appears
* twice in the document
*/
Element root = getDocument().getRootElement();
root.detach();
/**
* gets the root element of the documents to merge (excluding master)
*/
org.jdom.Document documentToBeMerged = (org.jdom.Document) xmlFile.getDocument().clone();
Element tempRoot = documentToBeMerged.getRootElement();
/**
* gets all the elements which will be included in the resulting document
*/
for (int iTag = 0; iTag < tagsToMerge.length; iTag++) {
for (Object child : tempRoot.getChildren(tagsToMerge[iTag], tempRoot.getNamespace())) {
if (child instanceof Element) {
Element newElement = (Element) ((Element) child).clone();
newElement.detach();
newElement.setNamespace(root.getNamespace());
Iterator descendantsIter = newElement.getDescendants();
while (descendantsIter.hasNext()) {
Object descendant = descendantsIter.next();
if (descendant instanceof Element) {
((Element) descendant).setNamespace(root.getNamespace());
}
}
root.addContent(newElement);
}
}
}
setDocument(new Document(root));
}
/**
* Sorts the children elements of the document root according to the array order. The tags not
* found in the array remain in the same order but at the beginning of the document
* @param tagsToSort
* @throws AppBuilderException
*/
public void sort(java.lang.String[] tagsToSort) throws AppBuilderException {
/**
* gets the resulting document from the master document. Cloning the document is important. If
* you clone or copy an element, the copy keeps his owner and, as a result, the element appears
* twice in the document
*/
Element root = getDocument().getRootElement();
setDocument(new Document(sort(root, tagsToSort)));
}
public Element sort(Element root, java.lang.String[] tagsToSort) throws AppBuilderException {
Element tempRoot = (Element) root.clone();
tempRoot.detach();
tempRoot.removeContent();
/**
* Makes groups of elements by tag
*/
List eltLstLst = new ArrayList(tagsToSort.length);
for (int iTag = 0; iTag < tagsToSort.length; iTag++) {
List children = root.getChildren(tagsToSort[iTag], root.getNamespace());
List eltLst = new ArrayList();
if (children != null && !children.isEmpty()) {
for (Object child : children) {
if (child instanceof Content) {
Content newElement = (Content) ((Content) child).clone();
newElement.detach();
eltLst.add(newElement);
}
}
}
eltLstLst.add(iTag, eltLst);
}
/**
* Orders the content of the resulting document
*/
for (int iTag = 0; iTag < tagsToSort.length; iTag++) {
if (!((List) eltLstLst.get(iTag)).isEmpty()) {
tempRoot.addContent((List) eltLstLst.get(iTag));
}
}
/**
* the result
*/
return tempRoot;
}
/**
* Changes the default encoding
* @param encoding the standard name of the encoding
* @since 1.0
* @roseuid 3AAF4C6E027E
*/
public void setOutputEncoding(java.lang.String encoding) {
outputEncoding = encoding;
setOutputter();
}
private String getOutputEncoding() {
return outputEncoding;
}
/**
* @return the document tree
* @since 1.0
* @roseuid 3AB0FA640395
*/
public org.jdom.Document getDocument() {
return underlyingDocument;
}
/**
* @param doc
* @since 1.0
* @roseuid 3AB0FA640395
*/
public void setDocument(org.jdom.Document doc) {
underlyingDocument = doc;
}
/**
* Gets the size of the resulting xml document
* @return the size of the document in memory, given the encoding, <code>-1</code> if unknown.
* @throws AppBuilderException
*/
public long getDocumentSize() throws AppBuilderException {
if (getDocument() != null) {
long docSize;
String docStr = getOutputter().outputString(getDocument());
docSize = docStr.length();
if (getOutputEncoding().startsWith("UTF-16")) {
docSize *= 2;
}
return docSize;
}
return -1;
}
/**
* For each element in tagsToFind returns the value of the specified attribute or the value of the
* element if no attribute is found.
* @param tagsToFind
* @param attribute
* @return For each element in tagsToFind returns the value of the specified attribute or the
* value of the element if no attribute is found.
* @throws AppBuilderException
*/
public String[] getAttributeValues(String[] tagsToFind, String attribute)
throws AppBuilderException {
/**
* gets the resulting document from the master document. Cloning the document is important. If
* you clone or copy an element, the copy keeps his owner and, as a result, the element appears
* twice in the document
*/
org.jdom.Document resultDoc = (org.jdom.Document) getDocument().clone();
Element root = resultDoc.getRootElement();
int iTag;
/**
* Makes groups of elements by tag
*/
List eltLstLst = new ArrayList(tagsToFind.length);
for (iTag = 0; iTag < tagsToFind.length; iTag++) {
List eltLst = root.getChildren(tagsToFind[iTag]);
if (!eltLst.isEmpty()) {
if (!root.removeChildren(tagsToFind[iTag])) {
throw new AppBuilderException("Could not remove \""
+ tagsToFind[iTag] + "\" elements from \"" + getName() + "\"");
}
}
eltLstLst.add(iTag, eltLst);
}
if (eltLstLst.isEmpty()) {
return null;
}
String[] attributeValues = new String[eltLstLst.size()];
for (int i = 0; i < eltLstLst.size(); i++) {
List eltLst = (List) eltLstLst.get(i);
for (int j = 0; j < eltLst.size(); j++) {
Element e = (Element) eltLst.get(j);
if (attribute != null) {
attributeValues[i] = e.getAttributeValue(attribute);
} else {
attributeValues[i] = e.getText();
}
}
}
return attributeValues;
}
/**
* Looks for all the elements with the given tag in the root element and its children. returns
* <code>null</code> if nothing was found The values are unique in the array returned
* @return the array of all the values found
*/
public String[] getTagValues(String tagToFind) {
Collection result = new HashSet();
if (getDocument().getRootElement().getName().equals(tagToFind)) {
result.add(getDocument().getRootElement().getText());
}
Iterator iChildren = getDocument().getRootElement().getChildren(tagToFind).iterator();
while (iChildren.hasNext()) {
result.add(((Element) iChildren.next()).getText());
}
if (result.isEmpty()) {
return null;
}
return objectArray2StringArray(result.toArray());
}
/**
* Looks for all the attributes with the given name in the root element and its children. returns
* <code>null</code> if nothing was found. The values are unique in the array returned
* @return the array of all the values found
*/
public String[] getAttributeValues(String attributeToFind) {
Collection result = new HashSet();
if (getDocument().getRootElement().getAttribute(attributeToFind) != null) {
result.add(getDocument().getRootElement().getAttributeValue(
attributeToFind));
}
Iterator iChildren = getDocument().getRootElement().getChildren().iterator();
Element currentElement = null;
while (iChildren.hasNext()) {
currentElement = (Element) iChildren.next();
if (currentElement.getAttribute(attributeToFind) != null) {
result.add(currentElement.getAttributeValue(attributeToFind));
}
}
if (result.isEmpty()) {
return null;
}
return objectArray2StringArray(result.toArray());
}
/**
* Converts an array of objects into an array of strings
*/
private String[] objectArray2StringArray(Object[] objectArray) {
String[] result = new String[objectArray.length];
for (int i = 0; i < result.length; i++) {
result[i] = (String) objectArray[i];
}
return result;
}
private XMLOutputter getOutputter() {
return outputter;
}
private void setOutputter() {
Format format = Format.getPrettyFormat();
format.setTextMode(Format.TextMode.TRIM);
format.setEncoding(outputEncoding);
format.setIndent(" ");
outputter = new XMLOutputter(format);
}
}